#include "cm_net.h"

#include <sys/socket.h>
#include <netdb.h> //gethostbyname
#include <unistd.h> //close, read, write
#include <cstring> // mem*

static void get_host(string address, struct sockaddr_in& sockaddr) {
	struct hostent *server_name = gethostbyname(address.c_str());
	if (server_name == NULL)
		throw ConnectionError("No such host");
	memcpy(&sockaddr.sin_addr.s_addr, server_name->h_addr_list[0], server_name->h_length);
}

static struct sockaddr_in get_sockaddr(int port, string address=string()) {
	struct sockaddr_in sockaddr;
	memset(&sockaddr, 0, sizeof(sockaddr));
	sockaddr.sin_family = AF_INET;
	if (address.empty()) //empty = server
		sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	else
		get_host(address, sockaddr);
	sockaddr.sin_port = htons(port);
	return sockaddr;
}

/*
 * Sock
 */

void GenericSock::set_timeout(int seconds,	int useconds) {
	struct timeval tv = { WAIT_INTERVAL_SEC, WAIT_INTERVAL_USEC };
	if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(struct timeval)) < 0)
		throw ConnectionError("Could not set timeout");
}

void GenericSock::bind(const sockaddr_in& servaddr) {
	if (::bind(fd, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0)
		throw ConnectionError("Could not bind to the TCP socket");
}


GenericSock::~GenericSock() {
	close(fd);
}

/*
 * TcpChannel
 */

TcpChannel::TcpChannel(string address, const int port) {
	sock.set_timeout();
	struct sockaddr_in servaddr = get_sockaddr(port, address);
	if (connect(sock.fd, (sockaddr*) &servaddr, sizeof(sockaddr)) < 0)
		throw ConnectionError("Cannot make connection");
}

TcpChannel::TcpChannel(int sockfd) : sock(sockfd) {
	sock.set_timeout();
}

template<typename IoFunc, typename T>
static void loop(IoFunc func, int fd, T buf, size_t bytes) {
	while (bytes > 0) {
		ssize_t bytespassed = func(fd, buf, bytes);
		if (bytespassed == -1)
			throw ConnectionError("Failed to loop");
		bytes -= bytespassed;
	}
}

void TcpChannel::write_from(std::istream& in) {
	while (in) {
		char buff[MAX_MESSAGE_LENGTH];
		in.read(buff, MAX_MESSAGE_LENGTH);
		//write() is not guaranteed to write everything. just like send()
		loop(write, sock.fd, buff, in.gcount());
	}
}

void TcpChannel::read_into(std::ostream& out) {
	char buf[MAX_MESSAGE_LENGTH];

	//read() need not read full buff either. but returns 0 only when there is no more data
	while ( int res=read(sock.fd, buf, sizeof(buf)) ) {
		if (res < 0)
			throw ConnectionError("TCP failed to get size");
		out.write(buf, res);
	}
}

/*
 * TcpListener
 */

TcpListener::TcpListener(int port) {
	sock.bind(get_sockaddr(port));
}

void TcpListener::listen() {
	if (::listen(sock.fd, 2) < 0)
		throw ConnectionError("Could not listen to file descriptor");
}

TcpChannel TcpListener::accept() {
	struct sockaddr_in cliaddr;
	socklen_t clilen = sizeof(cliaddr);
	return TcpChannel(::accept(sock.fd, (struct sockaddr*) &cliaddr, &clilen));
}

/*
 * UdpChannel
 */

void UdpChannel::bind() {
	sock.bind(sockaddr);
}

UdpChannel::UdpChannel(sockaddr_in s) : sockaddr(s), last_addr_p() {

}

void UdpChannel::send(string cmd) {
	for (int i=0; i<2; i++) {
		int res = sendto(sock.fd, cmd.c_str(), cmd.length(), 0,
				(struct sockaddr*) &sockaddr, sizeof(sockaddr));
		if (res >= 0)
			return;
	}
	throw ConnectionError("sendto failed: ");
}

string UdpChannel::receive() {
	char buf[PACKET_SIZE] = { 0 };
	static struct sockaddr_in last_addr;

	/*	sock.set_timeout()  */
	socklen_t addr_len = sizeof(last_addr);
	for (int i = 0; i < 2; i++) {
		int res = recvfrom(sock.fd, buf, PACKET_SIZE, 0,
				(struct sockaddr*) &last_addr, &addr_len);
		if (res >= 0) {
			delete last_addr_p;
			last_addr_p = new UdpChannel(last_addr);
			return string(buf, res);
		}
	}
	throw ConnectionError("receive on UDP failed twice");
}

UdpServer::UdpServer() : UdpChannel(get_sockaddr(SERVER_UDP_PORT)) {
	this->bind();
}

UdpClient::UdpClient(string host_addr) : UdpChannel(get_sockaddr(SERVER_UDP_PORT, host_addr)) {
	sock.set_timeout();
}

